Skip to content

feat(plugin-expo-config-plugins): withRockAutolinking — Expo CNG config plugin#708

Draft
TomKalina wants to merge 1 commit into
callstackincubator:mainfrom
TomKalina:feat/expo-cng-config-plugin
Draft

feat(plugin-expo-config-plugins): withRockAutolinking — Expo CNG config plugin#708
TomKalina wants to merge 1 commit into
callstackincubator:mainfrom
TomKalina:feat/expo-cng-config-plugin

Conversation

@TomKalina
Copy link
Copy Markdown
Contributor

Summary

Draft for #707 — adds an Expo Config Plugin (withRockAutolinking) to @rock-js/plugin-expo-config-plugins that re-applies Rock's autolinking patches on every expo prebuild, so they survive Expo CNG regenerating ios/ + android/ from scratch.

User wires it once into app.config.ts:

export default {
  // …
  plugins: [
    '@rock-js/plugin-expo-config-plugins/withRockAutolinking',
    // …
  ],
};

After that, pnpm clear:prebuild (or any expo prebuild --clean) leaves a Podfile / project.pbxproj / Gradle files that Rock can drive — no manual npm create rock re-run after every prebuild.

What it patches

Each transform mirrors the one-shot transform in packages/create-app/src/lib/utils/initInExistingProject.ts:

File Transform
ios/Podfile use_native_modules!(config_command)use_native_modules!(['npx', 'rock', 'config', '-p', 'ios'])
ios/<App>.xcodeproj/project.pbxproj React Native build phase shellScript → sources Rock's CLI via require.resolve('rock/package.json') (handles both the default Community-CLI shellScript and the RN 0.83 format)
android/app/build.gradle cliFile = file(...react-native/cli.js)cliFile = file("../../node_modules/rock/dist/src/bin.js")
android/settings.gradle autolinkLibrariesFromCommand(...) → Rock's command, full configure block or just the inner call as a fallback

Each step is idempotent: repeated prebuilds converge on the same file. Already-patched content is detected and short-circuited.

Structure

  • packages/plugin-expo-config-plugins/src/lib/config-plugin/withRockAutolinking.ts
    • Four pure string-in/string-out helpers (patchPodfile, patchXcodeProject, patchAndroidBuildGradle, patchAndroidSettingsGradle)
    • Three withDangerousMod wrappers (withRockIosPodfile, withRockIosXcode, withRockAndroid)
    • Top-level composed ConfigPlugin exported as default + named withRockAutolinking
  • packages/plugin-expo-config-plugins/src/index.ts — re-exports withRockAutolinking + the patch helpers
  • packages/plugin-expo-config-plugins/src/lib/config-plugin/__tests__/withRockAutolinking.test.ts — 13 unit tests

Tests

 ✓ src/lib/config-plugin/__tests__/withRockAutolinking.test.ts (13 tests) 2ms

 Test Files  1 passed (1)
      Tests  13 passed (13)

Each helper covered for:

  • Community-CLI input (bare use_native_modules!, default shellScript, default cliFile, default autolinkLibrariesFromCommand)
  • Expo prebuild input where the call already has an argument (use_native_modules!(config_command), variable-substituted cliFile, etc.)
  • Idempotency (already-patched input → no-op)
  • Unrelated content stays untouched

Status: draft

Marking as draft because:

  1. patchXcodeProject is brittle. It matches the build phase shellScript with two literal Xcode-emitted strings. RN versions other than 0.83 and the latest Community-CLI default will silently miss. Either: extend the source set as users hit it, or rewrite to parse pbxproj (e.g. via @expo/config-plugins' IOSConfig.XcodeUtils). I'd appreciate a steer on which direction the project wants.
  2. Android settings.gradle fallback may over-match. autolinkLibrariesFromCommand could appear inside multiple extensions.configure blocks in unusual setups. The full-block pattern catches the common Expo-generated shape; the fallback regex replaces every occurrence. Wider real-world testing needed.
  3. No e2e / integration test against a fresh expo prebuild output yet — only unit tests against representative snippets. Once I have CI access to a Rock dev environment I can wire one up; happy to add it if there's an obvious harness already in the repo I missed.
  4. No README / docs update in packages/plugin-expo-config-plugins/README.md yet — wanted maintainer sign-off on plugin name + import path before I write user-facing docs.

Happy to take any of those further (e.g. switch to a pbxproj parser, scope the Android regex, add docs) before this gets merged.

Related

Re-applies Rock's autolinking patches on every `expo prebuild` so they
survive Expo CNG regenerating `ios/` + `android/` from scratch.

Mirrors the one-shot transforms in
`packages/create-app/src/lib/utils/initInExistingProject.ts`:

  - iOS Podfile: rewrite `use_native_modules!(...)` to call Rock's
    `npx rock config -p ios`.
  - iOS project.pbxproj: rewrite the React Native build phase
    shellScript to source Rock's CLI (handles both the Community-CLI
    default and the RN 0.83 format).
  - Android app/build.gradle: point `cliFile` at
    `node_modules/rock/dist/src/bin.js`.
  - Android settings.gradle: point `autolinkLibrariesFromCommand` at
    Rock.

Composed via three `withDangerousMod` steps (iOS Podfile, iOS Xcode,
Android Gradle pair) and exposed as a default-exported `ConfigPlugin`
so users can add it once to `app.config.ts` plugins:

```ts
plugins: ['@rock-js/plugin-expo-config-plugins/withRockAutolinking']
```

After that, `expo prebuild --clean` keeps the Rock patches without any
manual `npm create rock` re-run. Closes the CNG support gap described
in callstackincubator#707.

The four patch helpers are pure string-in / string-out and exported
for testability — 13 unit tests cover Community-CLI input, Expo
prebuild input, idempotency, and the unrelated-content no-op path on
each helper.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 12, 2026

Someone is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

@thymikee
Copy link
Copy Markdown
Member

cc @thiagobrez

TomKalina pushed a commit to TomKalina/rock that referenced this pull request May 12, 2026
TomKalina pushed a commit to TomKalina/rock that referenced this pull request May 12, 2026
…plugin.js

Required for Expo's `@expo/config-plugins` resolver to find the plugin
when listed in `app.config.ts plugins: ['@rock-js/plugin-expo-config-plugins']`.

- `app.plugin.js` at package root (ESM, re-exports default) — Expo's
  plugin resolver looks here before falling back to the main entry.
- `package.json`:
  - `exports` now lists `./app.plugin.js` so Node's package-exports
    gating doesn't block the subpath.
  - `files` includes `app.plugin.js` so npm/pnpm pack it.

Without these the resolver fails with:
  "No app.plugin.js file found in @rock-js/plugin-expo-config-plugins"

Follow-up to callstackincubator#708. Verified end-to-end in an Expo SDK 55 project: plugin
loads, withDangerousMod patches Podfile during `expo prebuild`, Rock's
autolinking returns the expected pod list, build proceeds.
@thiagobrez
Copy link
Copy Markdown
Collaborator

Hi @TomKalina! Thanks for the PR. I've looked at it quickly. Im wondering if this should be part of the prebuild process itself instead of a plugin 🤔

Im busy this week but will take a look at it soon 👌🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants